package LDraw.Files;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.TreeSet;
import javax.media.opengl.GL2;
import javax.swing.undo.UndoManager;
import Command.LDrawBFCCommand;
import Command.LDrawColorT;
import Command.LDrawLSynthDirective;
import Command.LDrawPart;
import Common.Box2;
import Common.Box3;
import Common.Matrix4;
import Common.Ray3;
import Common.Vector2f;
import Common.Vector3f;
import LDraw.Support.DispatchGroup;
import LDraw.Support.LDrawCustomMetaCommand;
import LDraw.Support.LDrawDirective;
import LDraw.Support.LDrawKeywords;
import LDraw.Support.LDrawUtilities;
import LDraw.Support.MatrixMath;
import LDraw.Support.Range;
import LDraw.Support.type.CacheFlagsT;
import Renderer.ILDrawCollector;
import Renderer.ILDrawRenderer;
//==============================================================================
//
//File: LDrawStep.h
//
//Purpose: Represents a collection of Lego bricks which compose a single
// step when constructing a model.
//
//Created by Allen Smith on 2/20/05.
//Copyright (c) 2005. All rights reserved.
//==============================================================================
public class LDrawStep extends LDrawContainer {
/**
* @uml.property name="stepRotationType"
* @uml.associationEnd
*/
LDrawStepRotationT stepRotationType;
/**
* @uml.property name="rotationAngle"
* @uml.associationEnd
*/
Vector3f rotationAngle; // in degrees
/**
* @uml.property name="cachedBounds"
* @uml.associationEnd
*/
Box3 cachedBounds; // cached bounds of the step
// Optimization variables
/**
* @uml.property name="stepFlavor"
* @uml.associationEnd
*/
LDrawStepFlavorT stepFlavor; // defaults to LDrawStepAnyDirectives
/**
* @uml.property name="colorOfAllDirectives"
* @uml.associationEnd readOnly="true"
*/
LDrawColorT colorOfAllDirectives;
String stepName;
// ---------- emptyStep
// -----------------------------------------------[static]--
//
// Purpose: Creates a new step ready to be edited, with nothing inside it.
//
// ------------------------------------------------------------------------------
public static LDrawStep emptyStep() {
LDrawStep newStep = new LDrawStep();
newStep.init();
return newStep;
}// end emptyStep
// ---------- emptyStepWithFlavor:
// ------------------------------------[static]--
//
// Purpose: Creates a new step ready to be edited, and prespecifies that
// only directives of the flavorType will be added.
//
// ------------------------------------------------------------------------------
public static LDrawStep emptyStepWithFlavor(LDrawStepFlavorT flavorType) {
LDrawStep newStep = LDrawStep.emptyStep();
newStep.setStepFlavor(flavorType);
return newStep;
}// end emptyStepWithFlavor:
//
// #pragma mark -
// ========== init
// ==============================================================
//
// Purpose: Creates a new step ready to be edited, with nothing inside it.
//
// ==============================================================================
public LDrawStep init() {
super.init();
stepRotationType = LDrawStepRotationT.LDrawStepRotationNone;
rotationAngle = Vector3f.getZeroVector3f();
stepFlavor = LDrawStepFlavorT.LDrawStepAnyDirectives;
cachedBounds = Box3.getInvalidBox();
return this;
}// end init
// ========== initWithLines:inRange:parentGroup:
// ================================
//
// Purpose: Parses a step beginning at the specified line of LDraw code.
//
// ==============================================================================
public LDrawStep initWithLines(ArrayList<String> lines, Range range,
DispatchGroup parentGroup) throws Exception {
String currentLine = null;
LDrawDirective commandClass = null;
Range commandRange = range;
ArrayList<LDrawDirective> directives = new ArrayList<LDrawDirective>();
int lineIndex = 0;
int insertIndex = 0;
super.initWithLines(lines, range, parentGroup);
cachedBounds = Box3.getInvalidBox();
DispatchGroup stepDispatchGroup = null;
stepDispatchGroup = new DispatchGroup();
if(parentGroup!=null)
stepDispatchGroup.extendsFromParent(parentGroup);
// Parse out the STEP command
if (range.length() > 0) {
currentLine = lines.get(range.getMaxRange());
// See if the line is a step delimiter. If the delimiter doesn't
// exist,
// it's implied (such as in a 1-step model). Otherwise, it marks the
// end
// of the step.
if (lineIsStepTerminator(currentLine)) {
// Nothing more to parse. Stop.
// range.decreseLength(1);
} else if (lineIsRotationStepTerminator(currentLine)) {
// Parse the rotation step.
if (parseRotationStepFromLine(currentLine) == false)
throw new Exception("BricksmithParseException"
+ ": Bad rotstep syntax");
// range.decreseLength(1);
}
}
// Convert each non-step-delimiter line into a directive, and add it to
// this step.
lineIndex = range.getLocation();
while (lineIndex <= range.getMaxRange()) {
currentLine = lines.get(lineIndex);
if (currentLine.length() > 0) {
commandClass = LDrawUtilities
.classForDirectiveBeginningWithLine(currentLine);
commandRange = LDrawDirective.rangeOfDirectiveBeginningAtIndex(
lineIndex, lines, range.getMaxRange());
LDrawDirective newDirective = null;
if (commandClass == null) {
newDirective = new LDrawDirective();
} else {
newDirective = commandClass.initWithLines(lines, commandRange, stepDispatchGroup);
}
if (newDirective instanceof LDrawCustomMetaCommand) {
if (lineIsStepName(currentLine)){
stepName = ((LDrawCustomMetaCommand)newDirective).getCommandValue();
}
}else if(newDirective instanceof LDrawBFCCommand) {
if(((LDrawBFCCommand)newDirective).getCommand().equals("INVERTNEXT")){
stepDispatchGroup.setInvertedNext(true);
}else if(((LDrawBFCCommand)newDirective).getCommand().equals("CW")){
stepDispatchGroup.setCCW(false);
}else if(((LDrawBFCCommand)newDirective).getCommand().equals("CCW")){
stepDispatchGroup.setCCW(true);
}
}
directives.add(insertIndex, newDirective);
lineIndex = commandRange.getMaxRange() + 1;
insertIndex += 1;
} else {
lineIndex += 1;
}
}
// //todo
// #if USE_BLOCKS
// dispatch_group_notify(stepDispatchGroup, queue,
// ^{
// #endif
int counter = 0;
LDrawDirective currentDirective = null;
// Add the accumulated directives *in order*
for (counter = 0; counter < insertIndex; counter++) {
currentDirective = directives.get(counter);
addDirective(currentDirective);
}
// todo
// #if USE_BLOCKS
// // Now that the step is complete, we can release our lock on the
// // parent group and allow it to finish.
// if(parentGroup != null)
// {
// dispatch_group_leave(parentGroup);
// }
// });
// dispatch_release(stepDispatchGroup);
// #endif
return this;
}// end initWithLines:inRange:
// ---------- rangeOfDirectiveBeginningAtIndex:inLines:maxIndex:
// ------[static]--
//
// Purpose: Returns the range from the beginning to the end of the step.
//
// ------------------------------------------------------------------------------
public static Range rangeOfDirectiveBeginningAtIndex(int index,
ArrayList<String> lines, int maxIndex) {
String currentLine = null;
int counter = 0;
Range testRange = new Range(index, maxIndex - index + 1);
int stepLength = 0;
Range stepRange;
// Find the last line in the step. Steps either end with the step
// delimiter,
// or they simply go all the way to the end of the file.
// Convert each non-step-delimiter line into a directive, and add it to
// this
// step.
for (counter = testRange.getLocation(); counter <= testRange
.getMaxRange(); counter++) {
currentLine = lines.get(counter);
stepLength++;
// See if the line is a step delimiter. If the delimiter doesn't
// exist,
// it's implied (such as in a 1-step model). Otherwise, it marks the
// end
// of the step.
if (lineIsStepTerminator(currentLine)
|| lineIsRotationStepTerminator(currentLine)) {
// Nothing more to parse. Stop.
break;
}
}
stepRange = new Range(index, stepLength);
return stepRange;
}// end rangeOfDirectiveBeginningAtIndex:inLines:maxIndex:
//
//
// #pragma mark -
// #pragma mark DIRECTIVES
// #pragma mark -
// ========== draw:viewScale:parentColor:
// =======================================
//
// Purpose: Draw all the commands in the step.
//
// Certain steps are marked as having been optimized for fast
// drawing. Such steps consist entirely of one kind of directive,
// so we need call glBegin only once for the entire step.
//
// ==============================================================================
public void collectColor() {
ArrayList<LDrawDirective> commandsInStep = subdirectives();
// Draw each element in the step.
for (LDrawDirective currentDirective : commandsInStep) {
currentDirective.collectColor();
}
}// end draw:viewScale:parentColor:
// ========== drawSelf:
// ===========================================================
//
// Purpose: Draw this directive and its subdirectives by calling APIs on
// the passed in renderer, then calling drawSelf on children.
//
// Notes: Steps, like most collections, simply pass down the drawSelf
// message. This is needed because parts "draw" themselves; they do
// not "collect" themselves.
//
// ================================================================================
public void drawSelf(GL2 gl2, ILDrawRenderer renderer) {
ArrayList<LDrawDirective> commandsInStep = subdirectives();
// Draw each element in the step.
// for (LDrawDirective currentDirective : commandsInStep) {
// currentDirective.drawSelf(gl2, renderer);
// }
LDrawDirective currentDirective;
for (int i=0; i<commandsInStep.size(); i++) {
currentDirective = commandsInStep.get(i);
currentDirective.drawSelf(gl2, renderer);
}
}// end drawSelf:
// ========== collectSelf:
// ========================================================
//
// Purpose: Collect this is called on each directive by its parents to
// accumulate _mesh_ data into a display list for later drawing.
// The collector protocol passed in is some object capable of
// remembering the collectable data.
//
// The step does this by recursively collecting its directives.
//
// ================================================================================
public void collectSelf(ILDrawCollector renderer) {
ArrayList<LDrawDirective> commandsInStep = subdirectives();
// Draw each element in the step.
for (LDrawDirective currentDirective : commandsInStep) {
currentDirective.collectSelf(renderer);
}
revalCache(CacheFlagsT.DisplayList);
}// end collectSelf:
// ========== debugDrawboundingBox
// ==============================================
//
// Purpose: Draw a translucent visualization of our bounding box to test
// bounding box caching.
//
// ==============================================================================
public void debugDrawboundingBox(GL2 gl2) {
ArrayList<LDrawDirective> commandsInStep = subdirectives();
// Draw each element in the step.
for (LDrawDirective currentDirective : commandsInStep) {
currentDirective.debugDrawboundingBox(gl2);
}
super.debugDrawboundingBox(gl2);
}// end debugDrawboundingBox
public void getRange( Matrix4 transform, float[] range )
{
ArrayList<LDrawDirective> commandsInStep = subdirectives();
int commandCount = commandsInStep.size();
LDrawDirective currentDirective = null;
int counter = 0;
// Draw all the steps in the model
for (counter = 0; counter < commandCount; counter++) {
currentDirective = commandsInStep.get(counter);
currentDirective.getRange(transform, range);
}
}
// ========== hitTest:transform:viewScale:boundsOnly:creditObject:hits:
// =======
//
// Purpose: Hit-test the geometry.
//
// ==============================================================================
public void hitTest(Ray3 pickRay, Matrix4 transform, LDrawDirective creditObject,
HashMap<LDrawDirective, Float> hits) {
ArrayList<LDrawDirective> commandsInStep = subdirectives();
int commandCount = commandsInStep.size();
LDrawDirective currentDirective = null;
int counter = 0;
// Draw all the steps in the model
for (counter = 0; counter < commandCount; counter++) {
currentDirective = commandsInStep.get(counter);
currentDirective.hitTest(pickRay, transform, creditObject, hits);
}
}
// ========== boxTest:transform:boundsOnly:creditObject:hits:
// ===================
//
// Purpose: Check for intersections with screen-space geometry.
//
// ==============================================================================
public boolean boxTest(Box2 bounds, Matrix4 transform, boolean boundsOnly,
LDrawDirective creditObject, TreeSet<LDrawDirective> hits) {
if (!MatrixMath
.VolumeCanIntersectBox(boundingBox3(), transform, bounds)) {
return false;
}
ArrayList<LDrawDirective> commandsInStep = subdirectives();
int commandCount = commandsInStep.size();
LDrawDirective currentDirective = null;
int counter = 0;
// Draw all the steps in the model
for (counter = 0; counter < commandCount; counter++) {
currentDirective = commandsInStep.get(counter);
if (currentDirective.boxTest(bounds, transform, boundsOnly,
creditObject, hits))
if (creditObject != null)
return true;
}
return false;
}// end boxTest:transform:boundsOnly:creditObject:hits:
// ==========
// depthTest:inBox:transform:creditObject:bestObject:bestDepth:=======
//
// Purpose: depthTest finds the closest primitive (in screen space)
// overlapping a given point, as well as its device coordinate
// depth.
//
// ==============================================================================
public void depthTest(Vector2f testPt, Box2 bounds, Matrix4 transform,
LDrawDirective creditObject, ArrayList<LDrawDirective> bestObject,
FloatBuffer bestDepth) {
if (!MatrixMath.VolumeCanIntersectPoint(boundingBox3(), transform,
bounds, bestDepth.get(0))) {
return;
}
ArrayList<LDrawDirective> commandsInStep = subdirectives();
int commandCount = commandsInStep.size();
LDrawDirective currentDirective = null;
int counter = 0;
// Draw all the steps in the model
for (counter = 0; counter < commandCount; counter++) {
currentDirective = commandsInStep.get(counter);
currentDirective.depthTest(testPt, bounds, transform, creditObject,
bestObject, bestDepth);
}
}// end depthTest:inBox:transform:creditObject:bestObject:bestDepth:
// ========== write
// =============================================================
//
// Purpose: Write out all the commands in the step, prefaced by the line
// 0 STEP
//
// ==============================================================================
public String write() {
return writeWithStepCommand(false);
}// end write
// ========== writeWithStepCommand:
// =============================================
//
// Purpose: Write out all the commands in the step. The output will be
// postfaced by the line 0 STEP if explicitStep is true.
// The reason this method exists is that we do not want to write
// the step command for the last step in the file. That step
// is inferred rather than explicit.
//
// Note: flag is ignored if this is a rotation step. In that case, you
// get the step command no matter what.
//
// ==============================================================================
public String writeWithStepCommand(boolean flag) {
String written = new String();
String CRLF = "\r\n";
Vector3f angleZYX = rotationAngleZYX();
ArrayList<LDrawDirective> commandsInStep = subdirectives();
LDrawDirective currentCommand = null;
int numberCommands = commandsInStep.size();
int counter = 0;
// Write all the step's subdirectives
for (counter = 0; counter < numberCommands; counter++) {
currentCommand = commandsInStep.get(counter);
written = written.concat(String.format("%s%s",
currentCommand.write(), CRLF));
}
// End with 0 STEP or 0 ROTSTEP
if (flag == true
|| stepRotationType != LDrawStepRotationT.LDrawStepRotationNone) {
switch (stepRotationType) {
case LDrawStepRotationNone:
written = written.concat(String.format("0 %s",
LDrawKeywords.LDRAW_STEP_TERMINATOR));
break;
case LDrawStepRotationRelative:
written = written.concat(String.format(
"0 %s %.3f %.3f %.3f %s",
LDrawKeywords.LDRAW_ROTATION_STEP_TERMINATOR,
angleZYX.getX(), angleZYX.getY(), angleZYX.getZ(),
LDrawKeywords.LDRAW_ROTATION_RELATIVE));
break;
case LDrawStepRotationAbsolute:
written = written.concat(String.format(
"0 %s %.3f %.3f %.3f %s",
LDrawKeywords.LDRAW_ROTATION_STEP_TERMINATOR,
angleZYX.getX(), angleZYX.getY(), angleZYX.getZ(),
LDrawKeywords.LDRAW_ROTATION_ABSOLUTE));
break;
case LDrawStepRotationAdditive:
written = written.concat(String.format(
"0 %s %.3f %.3f %.3f %s",
LDrawKeywords.LDRAW_ROTATION_STEP_TERMINATOR,
angleZYX.getX(), angleZYX.getY(), angleZYX.getZ(),
LDrawKeywords.LDRAW_ROTATION_ADDITIVE));
break;
case LDrawStepRotationEnd:
written = written.concat(String.format("0 %s %s",
LDrawKeywords.LDRAW_ROTATION_STEP_TERMINATOR,
LDrawKeywords.LDRAW_ROTATION_END));
break;
}
}
// Now remove that last CRLF, if it's there.
if (written.length() !=0 && written.charAt(written.length() - 1) == '\n') {
written = written.substring(0, written.length() - CRLF.length());
}
return written;
}// end writeWithStepCommand:
//
//
// #pragma mark -
// #pragma mark DISPLAY
// #pragma mark -
// ========== browsingDescription
// ===============================================
//
// Purpose: Returns a representation of the directive as a short
// string
// which can be presented to the user.
//
// ==============================================================================
public String browsingDescription() {
LDrawModel enclosingModel = enclosingModel();
String description = null;
// If there is no parent model, just display the word step. This
// situtation
// would be highly irregular.
if (enclosingModel == null)
description = "Step";
else {
// Return the step number.
ArrayList<LDrawStep> modelSteps = enclosingModel.steps();
int stepIndex = modelSteps.indexOf(this);
description = new String("StepDisplayWithNumber "
+ ((long) stepIndex + 1));
}
return description;
}// end browsingDescription
// ========== iconName
// ==========================================================
//
// Purpose: Returns the name of image file used to display this kind
// of
// object, or null if there is no icon.
//
// ==============================================================================
public String iconName() {
String iconName = null;
switch (stepRotationType) {
case LDrawStepRotationNone:
// no image.
break;
case LDrawStepRotationEnd:
iconName = "RotationStepEnd";
break;
default:
iconName = "RotationStep";
break;
}
return iconName;
}// end iconName
// ========== inspectorClassName
// ================================================
//
// Purpose: Returns the name of the class used to inspect this one.
//
// ==============================================================================
public String inspectorClassName() {
return "InspectionStep";
}// end inspectorClassName
// #pragma mark -
// #pragma mark ACCESSORS
// #pragma mark -
// ========== boundingBox3
// ======================================================
// ==============================================================================
public Box3 boundingBox3() {
if (revalCache(CacheFlagsT.CacheFlagBounds) == CacheFlagsT.CacheFlagBounds) {
cachedBounds = LDrawUtilities
.boundingBox3ForDirectives(subdirectives());
}
return cachedBounds;
}// end boundingBox3
// ========== enclosingModel
// ====================================================
//
// Purpose: Returns the model of which this step is a part.
//
// ==============================================================================
public LDrawModel enclosingModel() {
return (LDrawModel) enclosingDirective();
}// end enclosingModel
// ========== rotationAngle
// =====================================================
//
// Purpose: Returns the xyz angle in degrees of the rotation. The
// value
// must
// be interpreted according to the step rotation type.
//
// ==============================================================================
public Vector3f rotationAngle() {
return rotationAngle;
}// end rotationAngle
// ========== rotationAngleZYX
// ==================================================
//
// Purpose: Returns the zyx angle in degrees of the rotation.
//
// Notes: Of of Bricksmith's matrix math functions expect angles to
// be in
// x-y-z order, so this value is not useful internally. However,
// the ROTSTEP directive (and the rest of MLCad) uses a z-y-x
// angle, so we have to save to the file in this format.
//
// ==============================================================================
public Vector3f rotationAngleZYX() {
// ---------- Convert XYZ to ZYX
// --------------------------------------------
// Translate our internal XYZ angle to ZYX by creating a rotation
// matrix and decomposing it in a different order.
Matrix4 rotationMatrix = MatrixMath.Matrix4Rotate(
Matrix4.getIdentityMatrix4(), rotationAngle);
Vector3f angleZYX = MatrixMath
.Matrix4DecomposeZYXRotation(rotationMatrix);
// convert from radians to degrees
angleZYX.setX((float) Math.toDegrees(angleZYX.getX()));
angleZYX.setY((float) Math.toDegrees(angleZYX.getY()));
angleZYX.setZ((float) Math.toDegrees(angleZYX.getZ()));
// ---------- Fix weird float values
// ----------------------------------------
// Sometimes these get decomposed with a -180 rotation, which is the
// same
// as
// a 180 rotation. Fix it for display purposes.
if (MatrixMath.FloatsApproximatelyEqual(angleZYX.getX(), -180.0f))
angleZYX.setX(180);
if (MatrixMath.FloatsApproximatelyEqual(angleZYX.getY(), -180.0f))
angleZYX.setY(180);
if (MatrixMath.FloatsApproximatelyEqual(angleZYX.getZ(), -180.0f))
angleZYX.setZ(180);
// Sometimes we wind up with a -0 rotation, which ought to be plain
// old
// 0.
// Fix it for display purposes.
if (MatrixMath.FloatsApproximatelyEqual(angleZYX.getX(), -0.0f))
angleZYX.setX(0);
if (MatrixMath.FloatsApproximatelyEqual(angleZYX.getY(), -0.0f))
angleZYX.setY(0);
if (MatrixMath.FloatsApproximatelyEqual(angleZYX.getZ(), -0.0f))
angleZYX.setZ(0);
return angleZYX;
}// end rotationAngleZYX
// ========== stepFlavor
// ========================================================
//
// Purpose: Returns the kind of step this is (optimized parts group
// like
// directives into a single step).
//
// ==============================================================================
public LDrawStepFlavorT stepFlavor() {
return stepFlavor;
}// end stepFlavor
// ========== stepRotationType
// ==================================================
//
// Purpose: Returns what kind of rotation is attached to this step.
//
// ==============================================================================
public LDrawStepRotationT stepRotationType() {
return stepRotationType;
}// end stepRotationType
// #pragma mark -
// ========== setModel:
// =========================================================
//
// Purpose: Sets a reference to the model of which this step is a
// part.
// Called automatically by -addStep:
//
// ==============================================================================
public void setModel(LDrawModel enclosingModel) {
setEnclosingDirective(enclosingModel);
}// end setModel:
// ========== setRotationAngle:
// =================================================
//
// Purpose: Sets the xyz angle (in degrees) of the receiver's
// rotation.
// The
// meaning of the value is determined by the step rotation type.
//
// Notes: The angle stored in the LDraw file is in zyx order, so it
// is
// unsuitable for feeding directly to this method.
//
// ==============================================================================
/**
* @param newAngle
* @uml.property name="rotationAngle"
*/
public void setRotationAngle(Vector3f newAngle) {
rotationAngle = newAngle;
}// end setRotationAngle:
// ========== setRotationAngleZYX:
// ==============================================
//
// Purpose: Sets the rotation angle (in degrees) such the the z angle
// is
// applied first, then y, and lastly x.
//
// Notes: This is the format in which ROTSTEP angles are saved in the
// file, but Bricksmith's matrix functions expect XYZ angles. This
// translates the ZYX angle so that it can be used by the rest of
// Bricksmith.
//
// ==============================================================================
public void setRotationAngleZYX(Vector3f newAngleZYX) {
Matrix4 rotationMatrix = Matrix4.getIdentityMatrix4();
Vector3f newAngleXYZ = Vector3f.getZeroVector3f();
rotationMatrix = MatrixMath.Matrix4Rotate(rotationMatrix,
MatrixMath.V3Make(0, 0, newAngleZYX.getZ()));
rotationMatrix = MatrixMath.Matrix4Rotate(rotationMatrix,
MatrixMath.V3Make(0, newAngleZYX.getY(), 0));
rotationMatrix = MatrixMath.Matrix4Rotate(rotationMatrix,
MatrixMath.V3Make(newAngleZYX.getX(), 0, 0));
newAngleXYZ = MatrixMath.Matrix4DecomposeXYZRotation(rotationMatrix);
// convert from radians to degrees
newAngleXYZ.setX((float) Math.toDegrees(newAngleXYZ.getX()));
newAngleXYZ.setY((float) Math.toDegrees(newAngleXYZ.getY()));
newAngleXYZ.setZ((float) Math.toDegrees(newAngleXYZ.getZ()));
// Sometimes these get decomposed with a -180 rotation, which is the
// same
// as
// a 180 rotation. Fix it for display purposes.
if (MatrixMath.FloatsApproximatelyEqual(newAngleXYZ.getX(), -180.0f))
newAngleXYZ.setX(180);
if (MatrixMath.FloatsApproximatelyEqual(newAngleXYZ.getY(), -180.0f))
newAngleXYZ.setY(180);
if (MatrixMath.FloatsApproximatelyEqual(newAngleXYZ.getZ(), -180.0f))
newAngleXYZ.setZ(180);
setRotationAngle(newAngleXYZ);
}// end setRotationAngleZYX:
// ========== setStepFlavor:
// ====================================================
//
// Purpose: Sets the step flavor, which identifies the types of
// LDrawDirectives the step contains. Setting the flavor to a
// specific directive type will cause the step to draw its
// subdirectives inside one set of glBegin()/glEnd(), rather than
// starting a new group for each directive encountered.
//
// ==============================================================================
/**
* @param newFlavor
* @uml.property name="stepFlavor"
*/
public void setStepFlavor(LDrawStepFlavorT newFlavor) {
stepFlavor = newFlavor;
}// end setStepFlavor:
// ========== setStepRotationType:
// ==============================================
//
// Purpose: Sets the kind of rotation attached to this step.
//
// Notes: Honoring a step rotation is the responsibility of the
// object
// drawing the model, not the step itthis.
//
// ==============================================================================
/**
* @param newValue
* @uml.property name="stepRotationType"
*/
public void setStepRotationType(LDrawStepRotationT newValue) {
stepRotationType = newValue;
}// end setStepRotationType:
//
// #pragma mark -
// ========== insertDirective:atIndex:
// ==========================================
//
// Purpose: Inserts the new directive into the step.
//
// ==============================================================================
public void insertDirective(LDrawDirective directive, int index) {
invalCache(CacheFlagsT.CacheFlagBounds);
invalCache(CacheFlagsT.DisplayList);
super.insertDirective(directive, index);
}// end insertDirective:atIndex:
// ========== removeDirectiveAtIndex:
// ===========================================
//
// Purpose: Removes the directive from the step.
//
// ==============================================================================
public void removeDirectiveAtIndex(int index) {
invalCache(CacheFlagsT.CacheFlagBounds);
invalCache(CacheFlagsT.DisplayList);
// LDrawDirective directive = subdirectives().get(index);
super.removeDirectiveAtIndex(index);
}// end removeDirectiveAtIndex:
//
// ========== lineIsStepTerminator:
// =============================================
//
// Purpose: Returns if line is a 0 STEP
//
// ==============================================================================
public static boolean lineIsStepTerminator(String line) {
if (line == null)
return false;
StringTokenizer strTokenizer = new StringTokenizer(line);
if (strTokenizer.hasMoreTokens() == false)
return false;
String parsedWord = strTokenizer.nextToken();
if (parsedWord.equals("0") == false)
return false;
if (strTokenizer.hasMoreTokens() == false)
return false;
parsedWord = strTokenizer.nextToken();
if (parsedWord.equals(LDrawKeywords.LDRAW_STEP_TERMINATOR) == false)
return false;
return true;
}
private boolean lineIsStepName (String line) {
if (line == null)
return false;
StringTokenizer strTokenizer = new StringTokenizer(line);
if (!strTokenizer.hasMoreTokens())
return false;
String parsedWord = strTokenizer.nextToken();
if (!(parsedWord.equals("0") && strTokenizer.hasMoreTokens()))
return false;
parsedWord = strTokenizer.nextToken();
if (!(parsedWord.equals(LDrawKeywords.MOCBUILDER_CUSTOM_META) && strTokenizer.hasMoreTokens()))
return false;
parsedWord = strTokenizer.nextToken();
if (!(parsedWord.equals(LDrawKeywords.MOCBUILDER_STEP_NAME) && strTokenizer.hasMoreTokens()))
return false;
return true;
}
public void setStepName (String name){
stepName = name;
LDrawCustomMetaCommand customMeta;
for (LDrawDirective directive:subdirectives()){
if (directive instanceof LDrawCustomMetaCommand) {
customMeta = (LDrawCustomMetaCommand) directive;
if (LDrawKeywords.MOCBUILDER_STEP_NAME.equals(customMeta.getCommandName())){
if ("".equals(name)) {
removeDirective(customMeta);
} else {
customMeta.setCommandValue(name);
}
return;
}
}
}
if (!"".equals(name)){
customMeta = new LDrawCustomMetaCommand();
customMeta.setCommandName(LDrawKeywords.MOCBUILDER_STEP_NAME);
customMeta.setCommandValue(name);
addDirective(customMeta);
}
}
public String getStepName (){
return stepName;
}
// ========== acceptsDroppedDirective:
// ==========================================
//
// Purpose: Returns true if this container will accept a directive
// dropped
// on
// it. Explicitly excludes LDrawLSynthDirectives such as
// INSIDE/OUTSIDE
// and this-referencing model "parts"
//
// ==============================================================================
public boolean acceptsDroppedDirective(LDrawDirective directive) {
// explicitly disregard LSynth directives
if (LDrawLSynthDirective.class.isInstance(directive)) {
return false;
}
// explicitly disregard this-references if the dropped directive is a
// model "part"
else if (LDrawPart.class.isInstance(directive)
&& ((LDrawPart) directive).referenceName() == ((LDrawMPDModel) enclosingModel())
.modelName()) {
return false;
}
return true;
}
// ========== lineIsRotationStepTerminator:
// =====================================
//
// Purpose: Returns if line is a 0 ROTSTEP
//
// ==============================================================================
public static boolean lineIsRotationStepTerminator(String line) {
if (line == null)
return false;
StringTokenizer strTokenizer = new StringTokenizer(line);
if (strTokenizer.hasMoreTokens() == false)
return false;
String parsedWord = strTokenizer.nextToken();
if (parsedWord.equals("0") == false)
return false;
if (strTokenizer.hasMoreTokens() == false)
return false;
parsedWord = strTokenizer.nextToken();
if (parsedWord.equals(LDrawKeywords.LDRAW_ROTATION_STEP_TERMINATOR) == false)
return false;
return true;
}
// ========== parseRotationStepFromLine:
// ========================================
//
// Purpose: Parses out the rotation step values from the given line.
//
// Rotation steps can have the following forms:
//
// 0 ROTSTEP angleX angleY angleZ // implied REL
// 0 ROTSTEP angleX angleY angleZ REL
// 0 ROTSTEP angleX angleY angleZ ABS
// 0 ROTSTEP angleX angleY angleZ ADD
// 0 ROTSTEP END
//
// Notes: The angle in ROTSTEPs is in z-y-x order, which is backwards
// from
// how Bricksmith expects the world to be.
//
// Returns: true on success.
//
// ==============================================================================
public boolean parseRotationStepFromLine(String rotstep) {
Vector3f angles = Vector3f.getZeroVector3f();
boolean success = true;
try {
if (rotstep.contains("0") == false)
throw new Exception("BricksmithParseException: "
+ "Bad ROTSTEP syntax");
if (rotstep.contains(LDrawKeywords.LDRAW_ROTATION_STEP_TERMINATOR) == false)
throw new Exception("BricksmithParseException: "
+ "Bad ROTSTEP syntax");
// Is it an end rotation?
if (rotstep.contains(LDrawKeywords.LDRAW_ROTATION_END) == true) {
setStepRotationType(LDrawStepRotationT.LDrawStepRotationEnd);
} else {
StringTokenizer strTokenizer = new StringTokenizer(rotstep);
String parsedString = strTokenizer.nextToken(); // 0
parsedString = strTokenizer.nextToken(); // ROTSTEP
// ---------- Angles
// ------------------------------------------------
parsedString = strTokenizer.nextToken(); // angleX
angles.setX(Float.parseFloat(parsedString));
parsedString = strTokenizer.nextToken(); // angleY
angles.setY(Float.parseFloat(parsedString));
parsedString = strTokenizer.nextToken(); // angleZ
angles.setZ(Float.parseFloat(parsedString));
// ---------- Rotation Type
// -----------------------------------------
if(strTokenizer.hasMoreTokens()==false)
setStepRotationType(LDrawStepRotationT.LDrawStepRotationRelative);
else{
parsedString = strTokenizer.nextToken(); // Rotation Type
if(parsedString.equals(LDrawKeywords.LDRAW_ROTATION_ABSOLUTE))
setStepRotationType(LDrawStepRotationT.LDrawStepRotationAbsolute);
else if(parsedString.equals(LDrawKeywords.LDRAW_ROTATION_ADDITIVE))
setStepRotationType(LDrawStepRotationT.LDrawStepRotationAdditive);
else if(parsedString.equals(LDrawKeywords.LDRAW_ROTATION_RELATIVE))
setStepRotationType(LDrawStepRotationT.LDrawStepRotationRelative);
}
setRotationAngleZYX(angles);
}
} catch (Exception e) {
success = false;
System.out.println("BricksmithParseException: "
+ "Bad ROTSTEP syntax");
}
return success;
}// end parseRotationStepFromLine:
// ========== registerUndoActions
// ===============================================
//
// Purpose: Registers the undo actions that are unique to this
// subclass,
// not to any superclass.
//
// ==============================================================================
public void registerUndoActions(UndoManager undoManager) {
super.registerUndoActions(undoManager);
// todo
// [[undoManager prepareWithInvocationTarget:this]
// setRotationAngle:[this
// rotationAngle]();
// [[undoManager prepareWithInvocationTarget:this]
// setStepRotationType:[this
// stepRotationType]();
// [undoManager setActionName:NSLocalizedString(@"UndoAttributesStep",
// null)();
}// end registerUndoActions:
// public Object clone() throws CloneNotSupportedException {
// LDrawStep a = (LDrawStep) super.clone();
//// a.rotationAngle = (Vector3f) rotationAngle.clone();
//// a.cachedBounds = (Box3) cachedBounds.clone();
// return a;
// }
// public void setSelected(boolean flag) {
// for(LDrawDirective directive : subdirectives())
// directive.setSelected(flag);
// }
}